/*   This file is a part of the WBTS Project. WBTS is a system for helping 
 *	 researchers, and browser implementers to find security related bugs.
 *   Copyright (C) 2010 Isaac Dawson
 *
 *    WBTS is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 TestCase object - This contains all of the data
 for the individual cases. 1 object per case.
*/
function TestCase() {
	this.logger_url = '/tc/saveTest';
	this.prelogger_url = '/tc/savePreTest';
	this.testcase_url = document.location.toString();
	this.input = '';
	this.output = '';
	this.description = '';
	this.target_host = '';
	this.result = '';
	this.expected_result = '';
	this.test_passed = '';
	this.has_sent = false;
	this.has_pre_sent = false;
	// make sure our cookies are reset for each test.
	document.cookie = '';
	
}
/*
toString - For debug purposes.
*/
TestCase.prototype.toString = function() {
	var data = "\r\nTestCase URL: " + this.testcase_url;
	data += "\r\nDescription: " + this.description;
	data += "\r\nTarget Host: " + this.target_host;
	data += "\r\nResult: " + this.result;
	data += "\r\nExpected Result: " + this.expected_result;
	data += "\r\nTest Passed: " + this.test_passed;
	data += "\r\nHas Been Sent: " + this.has_sent;
	data += "\r\nInput: " + this.input;
	data += "\r\nOutput Data: " + this.output;
	return data;
}

/*
saveTest - escapes the testcase data and fires it off
to the logger_url to be stuffed into the mongoDB.
*/
TestCase.prototype.saveTest = function(cb) {
	// hack because of firefox sending twice.
	if (this.has_sent == true) {
		return;
	}
    var params = this.escapeParams(false);
	this.has_sent = true;
	this.sendPostRequest(this.logger_url, params, cb);
}
/*
savePreTest - Sets up the result data prior to running
our test, if for some reason the test fails miserably 
(i.e. we totally fail out the interpreter) then we can
safely fail the test by calling failTest from the wbts runner.
*/
TestCase.prototype.savePreTest = function(cb) {
	if (this.has_pre_sent == true) {
		return;
	}
	var params = this.escapeParams(true);
	this.has_pre_sent = true;
	this.sendPostRequest(this.prelogger_url, params, cb);
}

TestCase.prototype.sendPostRequest = function(url, params, cb) {
	this.sendRequest('POST', url, params, cb);
}
TestCase.prototype.sendGetRequest = function(url, cb) {
	this.sendRequest('GET', url, null, cb);
}
TestCase.prototype.sendRequest = function(method, url, params, cb) {
	var xhr = get_xhr();
	var testcase = this; // set a ref as our context will change in onreadystatechange
	xhr.open(method, url, true);
	xhr.onreadystatechange = function() {
		if (xhr.readyState==4) {
			if( cb != null) {
				cb(xhr, testcase);
			}
        }
    }
    xhr.send(params)
}
TestCase.prototype.showRequest = function(cb) {
    if (cb == null) {
		var cb = function(data) {
            var pre_ele = document.createElement('pre');
            var txt = document.createTextNode(data.responseText);
	        pre_ele.appendChild(txt);
			document.body.appendChild(pre_ele);
		}
	}
	this.sendGetRequest('showRequest.rpy', cb);
}

TestCase.prototype.display = function(ele) {
	if (ele == null) {
		ele = document.createElement('pre');
	}
	var txt = document.createTextNode(this.toString());
    ele.appendChild(txt);
	document.body.appendChild(ele);
}

TestCase.prototype.escapeParams = function(is_pretest) {
	var params = "testcase_url=" + escape(this.testcase_url);
    params += "&description=" + escape(this.description);
    params += "&target_host=" + escape(this.target_host);
    params += "&expected_result=" + escape(this.expected_result);
	params += '&input=' + escape(this.input);
	params += '&output=' + escape(this.output);
	// we only send result & passed / failed if final test
	if (is_pretest == false) {
		params += "&result=" + escape(this.result);
		params += "&test_passed=" + escape(this.test_passed);
	}
	return params;
}

TestCase.prototype.clearCookies = function() {
	var cookies = document.cookie.split(";");
	for (var i = 0; i < cookies.length; i++) {
		var cookie = cookies[i];
		var idx = cookie.indexOf("=");
		var name = '';
		if (idx == -1) {
			document.cookie = '=;expires=Thu, 01 Jan 1970 00:00:00 GMT';
		} else {
			name = idx > -1 ? cookie.substr(0, idx) : cookie;
			document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT';
		}
	}
}

// for error reporting
TestCase.prototype.outputException = function(e) {
	if (e.message) {
		return e.message + '\n';
	}
	return e + '\n';
}

// If for some reason a browser doesn't support contentWindow.document
// we can easily modify it for all tests in the future.
// this is good for iframes/object tags, for windows see the Window related accessors.
TestCase.prototype.readOriginData = function(ifr, data) {
	return ifr.contentWindow.document.getElementById(data).innerHTML;
}

TestCase.prototype.readSameOriginData = function(ifr) {
	return this.readOriginData(ifr, 'readme');
}

TestCase.prototype.readCrossOriginData = function(ifr) {
	return this.readOriginData(ifr, 'cantreadme');
}

TestCase.prototype.readOriginBody = function(ifr) {
	return ifr.contentWindow.document.body.innerHTML;
}

TestCase.prototype.checkOriginDocument = function(ifr) {
	return ifr.contentWindow.document;
}

TestCase.prototype.getOriginDocument = function(ifr) {
	return this.checkOriginDocument(ifr);
}

// Window related accessor functions //
TestCase.prototype.readWindowOriginData = function(wind, data) {
	return wind.document.getElementById(data).innerHTML;
}

TestCase.prototype.readWindowSameOriginData = function(wind) {
	return this.readWindowOriginData(wind, 'readme');
}

TestCase.prototype.readWindowCrossOriginData = function(wind) {
	return this.readWindowOriginData(wind, 'cantreadme');
}

TestCase.prototype.readWindowOriginBody = function(wind) {
	return wind.document.body.innerHTML;
}

TestCase.prototype.checkWindowOriginDocument = function(wind) {
	return wind.document;
}

TestCase.prototype.getWindowOriginDocument = function(wind) {
	return this.checkWindowOriginDocument(wind);
}

/*****************************************************************************/
/* Misc. TestCase Functions                                                  */
/*****************************************************************************/

// takes the xhr object that has our response data
// and the testcase object
function tc_parsehdrs_for_crlf(xhr, tc) {
	tc_parsehdrs_for_byte(xhr, tc, 10);
}
function tc_parsehdrs_for_lf(xhr, tc) {
	tc_parsehdrs_for_byte(xhr, tc, 10);
}
function tc_parsehdrs_for_cr(xhr, tc) {
	tc_parsehdrs_for_byte(xhr, tc, 13);
}


function tc_parsehdrs_for_byte(xhr, tc, byte_val) {
	var request = xhr.responseText;
	var inj_index = request.indexOf('x-injheader');
	if (inj_index == -1) {
		tc.test_passed = 'true';
		tc.result = "undefined";
	} else if (request.charAt(inj_index-1) == String.fromCharCode(byte_val)) {
		tc.test_passed = 'false';
		tc.result = "'"+request.charAt(inj_index-1)+"'";
	} else {
		tc.test_passed = 'true';
		tc.result = "'"+request.charAt(inj_index-1)+"'";
	}
	tc.output = request;
	tc.clearCookies();
	tc.saveTest(null);
}

// not really tested...
// assign tc.disallowed to something if you want to search
// for something other than 'should not see me'.
function tc_parse_upload_for_data(xhr, tc) {
	var request = xhr.responseText;
	var inj_index = null;
	if (tc.disallowed == undefined) {
		inj_index = request.indexOf(tc.disallowed);
	} else {
		inj_index = request.indexOf('should not see me');
	}
	
	if (inj_index == -1) {
		tc.test_passed = 'true';
		tc.result = "not found";
	} else {
		tc.test_passed = 'false';
		tc.result = "forbidden data found in response.";
	}
	tc.output = request;
	tc.saveTest(null);
}

/* Various helper functions */
function get_xhr() {
    if (window.XMLHttpRequest) {
        var xmlhttp = new XMLHttpRequest();
    } else {
        var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
	// are we on windows mobile?
	// 
	return xmlhttp;
}